/*
 *
 *  *    Copyright © OpenAtom Foundation.
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *         http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.inspur.edp.commonmodel.engine.api.data;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.GspBusinessEntity;
import com.inspur.edp.bef.bizentity.common.BefDtBeanUtil;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.designtime.api.util.MetadataUtil;
import com.inspur.edp.cef.entity.entity.AssoInfoBase;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonModel;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.metadata.rtcustomization.api.CustomizationRtService;
import com.inspur.edp.metadata.rtcustomization.api.CustomizationService;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import lombok.var;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@JsonSerialize(using = AssoInfoJsonSerializer.class)
public class AssociationInfo extends AssoInfoBase implements IAssociationInfo, Cloneable {

  private GspAssociation association;
  private HashMap<String, Object> values;
  private IGspCommonModel refModel;
  private final Object obj = new Object();
  public AssociationInfo(GspAssociation association) {
    this();
    Objects.requireNonNull(association, "association");
    this.association = association;
    initRefModel();
  }

  public AssociationInfo() {
  }

  public void setAssociation(GspAssociation asso) {
    this.association = asso;
    initRefModel();
  }

  public final GspAssociation getAssociation()
  {return association;}

  private void initRefModel(){
    GspMetadata refMetadata = MetadataUtil.getCustomRTMetadata(association.getRefModelID());
    Objects.requireNonNull(refMetadata, association.getBelongElement().getLabelID()+"的关联元数据:" + association.getRefModelID());
    refModel = (IGspCommonModel) refMetadata.getContent();
    Objects.requireNonNull(refModel, "be");
  }
//  public void checkValues() {
//    if (association == null) {
//      throw new RuntimeException("association is null.");
//    }
//
//    for (Map.Entry<String, Object> pair : getValues().entrySet()) {
//      //暂不检查value, 只检查字段名是否存在
//      checkRefElementExists(pair.getKey());
//    }
//  }

  public Map<String, Object> getValues() {
    if (values == null) {
      values = new HashMap<>(
          association == null ? 16 : association.getRefElementCollection().size() + 1);
    }
    return values;
  }

  @Override
  protected Object innerGetValue(String labelId) {
    Objects.requireNonNull(labelId, "labelId");
    checkRefElementExists(labelId);
    return getValues().get(labelId);
  }

  private HashSet<String> refs;

  //VO变更集转BE变更集的时候，解析AssociationInfo 用clone的方式错误赋值了refs(vo上带出字段少。)。
  private void checkRefElementExists(String labelId) {
    if (association == null || association.getRefElementCollection() == null) {
      return;
    }
    if (association.getBelongElement().getLabelID().equals(labelId)) {
      return;
    }

    if (refs == null) {
      synchronized (obj){
        if(refs == null){
          refs = new HashSet<>(association.getRefElementCollection().size());
          for (IGspCommonField refElement : association.getRefElementCollection()) {
            refs.add(refElement.getLabelID());
          }
        }
      }
    }
    if (!refs.contains(labelId)) {
      throw new RuntimeException(
              "关联带出字段不存在" + association.getBelongElement().getLabelID() + "." + labelId);
    }
  }

  @Override
  public ICefData copySelf() {
    return this;
  }

  @Override
  public ICefData copy() {
    return this;
  }

  @Override
  public List<String> getPropertyNames() {
    List<String> rez =
        association.getRefElementCollection().stream()
            .map(item -> item.getLabelID())
            .collect(Collectors.toList());
    rez.add(association.getBelongElement().getLabelID());
    return rez;
  }

  @Override
  protected void innerSetValue(String labelId, Object value) {
    Objects.requireNonNull(labelId, "labelId");
    checkRefElementExists(labelId);
    getValues().put(labelId, value);
  }

  @Override
  public String getValue() {
    if (association == null) {
      throw new RuntimeException("association is null.");
    }
    return (String) getValue(association.getBelongElement().getLabelID());
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    AssociationInfo result = (AssociationInfo) super.clone();
    //存在VO上带出字段和BE不一致的情况,需要进行重置
    result.refs = null;
    if (values != null) {
      result.values = cloneForValues(values);
    }
    return result;
  }

  //TODO
  private static <TKey, TValue> HashMap<TKey, TValue> cloneForValues(HashMap<TKey, TValue> source)
      throws CloneNotSupportedException {
    if (source == null) {
      return null;
    }
    HashMap<TKey, TValue> result = new HashMap<>(source.size(), 1);
    for (Map.Entry<TKey, TValue> pair : source.entrySet()) {
      Object value =
          pair.getValue() != null && pair.getValue() instanceof AssociationInfo
              ? ((AssociationInfo) pair.getValue()).clone()
              : pair.getValue();
      result.put(pair.getKey(), (TValue) value);
    }
    return result;
  }

  @Deprecated
  public static AssociationInfo createData(GspAssociation gspAsso) {
    return new AssociationInfo(gspAsso);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null || !(obj instanceof AssociationInfo)) {
      return false;
    }
    AssociationInfo other = (AssociationInfo) obj;
    if (isEmptyValues(values)) {
      return isEmptyValues(other.values);
    }
    if (isEmptyValues(other.values)) {
      return false;
    }
    Map<String, Object> otherValues = new HashMap<>(other.values);
    for (Map.Entry<String, Object> pair : values.entrySet()) {
      if (!valueEquals(pair.getValue(), otherValues.remove(pair.getKey()))) {
        return false;
      }
    }
    return isEmptyValues(otherValues);
  }

  private static boolean isEmptyValues(Map<String, Object> values) {
    return values == null
        || values.isEmpty()
        || values.values().stream().allMatch(item -> item == null);
  }

  private static boolean valueEquals(Object value1, Object value2) {
    if (value1 != null && value1 instanceof byte[] && value2 != null && value2 instanceof byte[]) {
      return Arrays.equals((byte[]) value1, (byte[]) value2);
    }
    if (value1 == null) {
      return value2 == null;
    }
    return value1.equals(value2);
  }

  @Override
  public Object innerCreateValue(String propName) {
    for(var refElement : association.getRefElementCollection()){
      if(!refElement.getLabelID().equals(propName))
        continue;
      if(refElement.getObjectType() != GspElementObjectType.Association)
        continue;
      if(refElement.getIsRefElement()) {
        IGspCommonElement refBEElement = refModel.findElementById(refElement.getRefElementId());
        GspMetadata metadata = MetadataUtil.getCustomRTMetadata(refElement.getParentAssociation().getRefModelID());
        GspBusinessEntity gspBusinessEntity = (GspBusinessEntity) metadata.getContent();
        GspBizEntityObject gspBizEntityObject = gspBusinessEntity.getNode(refElement.getParentAssociation().getRefObjectCode());
        for (IGspCommonField gspCommonField : gspBizEntityObject.getContainElements()) {
          if (gspCommonField.getID().equals(refElement.getRefElementId())) {
            return createAssociationInfo(gspCommonField.getChildAssociations().get(0));
          }
        }
      }
      return createAssociationInfo(association);
    }
    return null;
  }

  protected AssociationInfo createAssociationInfo(GspAssociation association){
    return new AssociationInfo(association);
  }
}
